package org.smile.auth.session.redis;


import org.smile.auth.session.Manager;
import org.smile.auth.session.Store;
import org.smile.cache.redis.RedisCallback;
import org.smile.cache.redis.RedisTemplate;
import org.smile.io.serialize.Serializer;
import org.smile.io.serialize.StringSerializer;
import org.smile.util.Md5;
import redis.clients.jedis.Jedis;

import java.util.*;

/**
 * 使用redis实现的一个session存贮
 * @author 胡真山
 *
 */
public class RedisStore implements Store<RedisSession>{
	/**获取set缓存对象的分页操作*/
	protected static int PAGE_SIZE=128;
	/***/
	protected Manager manager;
	/**过期时间*/
	protected int expireTime=3600;
	/**缓存的前缀*/
	protected String prefix="session:";
	protected String tokenPrefix="session~token:";
	/**缓存key的前缀*/
	protected String keysPrefix="session~keys:";
	/**缓存key的前缀*/
	protected String attributesPrefix="session~attr:";
	/**
	 * redis数据库操作的模板
	 */
	protected RedisTemplate redisTemplate;
	/**
	 * 用于对缓存键的序列化
	 */
	protected Serializer<String> keySerializer=new StringSerializer();
	/**
	 * 用于对内容的序列化
	 */
	protected Serializer<Object> serializer;

	@Override
	public RedisSession load(final String sessionId) {
		return load(sessionKey(sessionId));
	}

	@Override
	public RedisSession loadByToken(String token) {
		return load(sessionTokenKey(token));
	}

	protected RedisSession load(byte[] key) {
		RedisSessionInfo info=redisTemplate.execute(new RedisCallback<RedisSessionInfo>(){
			@Override
			public RedisSessionInfo onCallRedis(Jedis jedis) {
				byte[]  infos=jedis.get(key);
				if(infos!=null){
					return (RedisSessionInfo)serializer.deserialize(infos);
				}
				return null;
			}});
		if(info!=null){
			RedisSession session=(RedisSession) manager.createSession(info.getId());
			info.toSession(session);
			session.setNew(false);
			session.setMaxInactiveInterval(manager.getSessionMaxAliveTime());
			session.setValid(true);
			session.access();
			session.setStore(this);
			return session;
		}
		return null;
	}
	/***
	 * 加载所有的属性
	 * @param session
	 */
	public void loadAttributes(final RedisSession session){
		final String sessionId=session.getId();
		//获取session的属性值
		Map<String,Object> sessionMap=redisTemplate.execute(new RedisCallback<Map<String,Object>>() {
			@Override
			public Map<String,Object> onCallRedis(Jedis jedis) {
					Set<byte[]> keys=doGetSet(jedis, sessionKeysKey(sessionId));
					if(keys.size()>0){
						byte[][] keyBytes=convertKeySet(keys);
						List<byte[]> bs = jedis.hmget(sessionAttrKey(sessionId),keyBytes);
						Map<String,Object> result=new HashMap<String, Object>();
						int i=0;
						for(byte[] b:keys){
							String key=keySerializer.deserialize(b);
							Object value=serializer.deserialize(bs.get(i));
							result.put(key, value);
							i++;
						}
						return result;
					}
					return null;
				}
			});
		if(sessionMap!=null&&sessionMap.size()>0){
			for(Map.Entry<String, Object> entry:sessionMap.entrySet()){
				session.setValue(entry.getKey(), entry.getValue());
			}
		}
	}
	
	/**
	 * attribute名称转成可获取键的数组
	 * @param bytes
	 * @return
	 */
	private byte[][] convertKeySet(Set<byte[]> bytes){
		if(bytes.size()>0){
			byte[][] byteArr=new byte[bytes.size()][];
			int i=0;
			for(byte[] b:bytes){
				byteArr[i++]=b;
			}
			return byteArr;
		}
		return new byte[0][];
	}
	
	/**键缓存的key*/
	protected byte[] sessionKeysKey(String sessionid){
		String key=keysPrefix+sessionid;
		return keySerializer.serialize(key);
	}
	/**
	 * redis缓存的主体服务的键值
	 * @param sessionId
	 * @return
	 */
	protected byte[] sessionKey(String sessionId){
		String key=prefix+sessionId;
		return keySerializer.serialize(key);
	}

	protected byte[] sessionTokenKey(String token){
		String key=tokenPrefix+ Md5.encrypt(token);
		return keySerializer.serialize(key);
	}
	
	/**
	 * 用于存储session的attribute内容的键
	 * @param sessionId
	 * @return
	 */
	protected byte[] sessionAttrKey(String sessionId){
		String key=attributesPrefix+sessionId;
		return keySerializer.serialize(key);
	}

	@Override
	public void saveBase(RedisSession session){
		final byte[] sessionKey=sessionKey(session.getId());
		saveBase(session,sessionKey);
	}
	public void saveBase(RedisSession session,final byte[] loadKey){
		redisTemplate.execute(new RedisCallback<Object>() {
			@Override
			public Object onCallRedis(Jedis jedis) {
				RedisSessionInfo info=new RedisSessionInfo();
				info.fromSession(session);
				jedis.set(loadKey, serializer.serialize(info));
				jedis.expire(loadKey, expireTime);
				return null;
			}
		});
	}

	@Override
	public void saveBaseByToken(RedisSession session){
		final byte[] sessionKey=sessionTokenKey(session.getToken());
		this.saveBase(session,sessionKey);
	}

	@Override
	public void save(final RedisSession session) {
		final byte[] sessionKey=sessionKey(session.getId());
		redisTemplate.execute(new RedisCallback<Object>() {
			@Override
			public Object onCallRedis(Jedis jedis) {
				RedisSessionInfo info=new RedisSessionInfo();
				info.fromSession(session);
				jedis.set(sessionKey, serializer.serialize(info));
				//属性key
				byte[] sessionAttrKey=sessionAttrKey(session.getId());
				Enumeration<String>  attributes=session.getAttributeNames();
				byte[] sessionKeysKey=sessionKeysKey(session.getId());
				while(attributes.hasMoreElements()){
					String name=attributes.nextElement();
					Object value=session.getAttribute(name);
					byte[] nameBytes=keySerializer.serialize(name);
					jedis.hset(sessionAttrKey,nameBytes,serializer.serialize(value));
					jedis.zadd(sessionKeysKey, 0, nameBytes);
				}
				jedis.expire(sessionAttrKey, expireTime);
				jedis.expire(sessionKey, expireTime);
				jedis.expire(sessionKeysKey, expireTime);
				return null;
			}
		});
	}

	@Override
	public boolean remove(String sessionId) {
		final byte[] sessionAttrKey=sessionAttrKey(sessionId);
		final byte[] sessionKeysKey=sessionKeysKey(sessionId);
		final byte[] sessionKey=sessionKey(sessionId);
		redisTemplate.execute(new RedisCallback<Boolean>() {
			@Override
			public Boolean onCallRedis(Jedis jedis) {
				jedis.del(sessionKey);
				jedis.del(sessionAttrKey);
				jedis.del(sessionKeysKey);
				return true;
			}
		});
		return false;
	}

	@Override
	public void setManager(Manager manager) {
		this.manager=manager;
	}

	public void setAttribute(final RedisSession session, final String name, Object value) {
		String sessionId=session.getId();
		final byte[] sessionAttrKey=sessionAttrKey(sessionId);
		final byte[] sessionKeysKey=sessionKeysKey(sessionId);
		final byte[] nameKey=keySerializer.serialize(name);
		final byte[] values=serializer.serialize(value);
		redisTemplate.execute(new RedisCallback<Object>() {
			@Override
			public Object onCallRedis(Jedis jedis) {
				jedis.hset(sessionAttrKey, nameKey, values);
				jedis.zadd(sessionKeysKey, 0, nameKey);
				jedis.expire(sessionAttrKey, expireTime);
				jedis.expire(sessionKeysKey, expireTime);
				session.onAttributeStoreChange(name, values);
				return null;
			}
		});
	}
	/**
	 * 保存数据
	 * @param session
	 * @param name
	 * @param valueBytes
	 */
	protected void storeBytes(RedisSession session, String name,final byte[] valueBytes) {
		String sessionId=session.getId();
		final byte[] sessionAttrKey=sessionAttrKey(sessionId);
		final byte[] sessionKeysKey=sessionKeysKey(sessionId);
		final byte[] nameKey=keySerializer.serialize(name);
		redisTemplate.execute(new RedisCallback<Object>() {
			@Override
			public Object onCallRedis(Jedis jedis) {
				jedis.hset(sessionAttrKey, nameKey, valueBytes);
				jedis.zadd(sessionKeysKey, 0, nameKey);
				jedis.expire(sessionAttrKey, expireTime);
				jedis.expire(sessionKeysKey, expireTime);
				return null;
			}
		});
	}

	@Override
	public void removeAttribute(final RedisSession session, final String name) {
		final String sessionId=session.getId();
		//属性
		final byte[] sessionAttrKey=sessionAttrKey(sessionId);
		//属性名
		final byte[] nameKey=keySerializer.serialize(name);
		redisTemplate.execute(new RedisCallback<Object>() {
			@Override
			public Object onCallRedis(Jedis jedis) {
				jedis.hdel(sessionAttrKey, nameKey);
				jedis.zrem(sessionKeysKey(sessionId),nameKey);
				session.onAttributeStoreChange(name, null);
				return null;
			}
		});
	}

	/**
	 * 设置redis操作模板
	 * 用于些存储对redis数据操作
	 * @param redisTemplate
	 */
	public void setRedisTemplate(RedisTemplate redisTemplate) {
		this.redisTemplate = redisTemplate;
	}
	
	/***
	 * 获取redis操作模板
	 * @return
	 */
	public RedisTemplate getRedisTemplate() {
		return redisTemplate;
	}
	/**
	 * 设置内容序列化工具
	 * @param serializer
	 */
	public void setSerializer(Serializer<Object> serializer) {
		this.serializer = serializer;
	}

	/**
	 * 设置缓存默认的过期时长
	 * @param expireTime
	 */
	public void setExpireTime(int expireTime) {
		this.expireTime = expireTime;
	}
	/**
	 * 获取一个set对象从redis中
	 * @param jedis
	 * @param keysKey
	 * @return
	 */
	public Set<byte[]> doGetSet(Jedis jedis,byte[] keysKey){
		Set<byte[]> keySet=new LinkedHashSet<byte[]>();
		int offset = 0;
		boolean finished = false;
		do {
			// need to paginate the keys
			Collection<byte[]> keys = jedis.zrange(keysKey, (offset) * PAGE_SIZE, (offset + 1) * PAGE_SIZE - 1);
			finished = keys.size() < PAGE_SIZE;
			offset++;
			if (!keys.isEmpty()) {
				keySet.addAll(keys);
			}
		} while (!finished);
		return keySet;
	}

	@Override
	public void access(RedisSession session) {
		String sessionId=session.getId();
		final byte[] sessionAttrKey=sessionAttrKey(sessionId);
		final byte[] sessionKeysKey=sessionKeysKey(sessionId);
		final byte[] sessionKey=sessionKey(sessionId);
		redisTemplate.execute(new RedisCallback<Object>() {
			@Override
			public Object onCallRedis(Jedis jedis) {
				jedis.expire(sessionAttrKey, expireTime);
				jedis.expire(sessionKeysKey, expireTime);
				jedis.expire(sessionKey, expireTime);
				return null;
			}
		});
	}

	@Override
	public Object getAttribute(final RedisSession session, final String name) {
		String sessionId=session.getId();
		final byte[] sessionAttrKey=sessionAttrKey(sessionId);
		final byte[] nameKey=keySerializer.serialize(name);
		Object value=redisTemplate.execute(new RedisCallback<Object>() {
			@Override
			public Object onCallRedis(Jedis jedis) {
				byte[] bytes=jedis.hget(sessionAttrKey, nameKey);
				if(bytes!=null){
					jedis.expire(sessionAttrKey, expireTime);
					session.onAttributeStoreChange(name, bytes);
					return serializer.deserialize(bytes);
				}
				return null;
			}
		});
		return value;
	}



	@Override
	public Enumeration<String> getAttributeNames(RedisSession session) {
		Set<String> names=getValueNames(session);
		return new Enumeration<String>(){
			Iterator<String>  iterator=names.iterator();
			@Override
			public boolean hasMoreElements() {
				return iterator.hasNext();
			}
			@Override
			public String nextElement() {
				return iterator.next();
			}};
	}

	@Override
	public Set<String> getValueNames(RedisSession session) {
		String sessionId=session.getId();
		final byte[] sessionAttrKey=sessionKeysKey(sessionId);
		final Set<String> names=redisTemplate.execute(new RedisCallback<Set<String>>() {
			@Override
			public Set<String> onCallRedis(Jedis jedis) {
				Set<String> keySet=new LinkedHashSet<String>();
				int offset = 0;
				boolean finished = false;
				do {
					// need to paginate the keys
					Collection<byte[]> keys = jedis.zrange(sessionAttrKey, (offset) * PAGE_SIZE, (offset + 1) * PAGE_SIZE - 1);
					finished = keys.size() < PAGE_SIZE;
					offset++;
					if (!keys.isEmpty()) {
						for(byte[] key:keys){
							keySet.add(keySerializer.deserialize(key));
						}
					}
				} while (!finished);
				return keySet;
			}
		});
		return names;
	}
}
